import numpy as np

# 为克服列表的缺点，一个 NumPy 数组只容纳一种数据类型，以节约内存。
# 为方便起见，可将 NumPy 数组简单分为整数型数组与浮点型数组。

arr1 = np.array([1,2,3])
print(arr1)
arr2 = np.array([1.0, 2, 3])
print(arr2)

# 往整数型数组里插入浮点数，该浮点数会自动被截断为整数；
# 整数型数组
arr1 = np.array( [1, 2, 3] )
arr1[0] = 100.9 # 插入浮点数，被截断，数组仍为整数型
print(arr1)
# 往浮点型数组里插入整数，该整数会自动升级为浮点数；
# 浮点型数组
arr2 = np.array( [1.0, 2, 3] )
arr2[1] = 10 # 插入整数型，被升级，数组仍为浮点型
print(arr2)

# 整数型数组和浮点型数组相互转换，规范的方法是使用 .astype( ) 方法。
# 整数型数组 ——> 浮点型数组
arr2 = arr1.astype(float)
print(arr2)
# 浮点型数组 ——> 整数型数组
arr3 = arr2.astype(int)
print(arr3)

# 三维数组的形状参数形如： (x,y,z) 。
# 当数组有 3 层中括号，如[[[1 2 3]]]，则其为三维数组，其形状是 (1,1,3)
arr3 = np.ones((1,1,3)) # 传入形状(1,1,3)
print(arr3) # 造出三维数组
print( arr3.shape )

# 大家可以随时留意一下数组的维度（通过中括号的数量），后面有些函数（比如数组的拼接函数）需要两个数组是同维度的
# 一维数组转二维数组，还是二维数组转一维数组，均要使用的是数组的重塑方法 .reshape( ) ，该方法需要传入重塑后的形状（shape）参数
# 给定了其他维度的数值，剩下一个维度可以填-1，让它自己去计算。
# 创建一维数组
arr1 = np.arange(10)
print(arr1)
# 升级为二维数组
arr2 = arr1.reshape( (1,-1) )
print(arr2)

# 递增数组使用 np.arange( ) 函数进行创建（arange 全称是 array_range）。
# 递增数组
arr1 = np.arange(10) # 从 0 开始，到 10 之前结束
print(arr1)
# 递增数组
arr3 = np.arange(1,21,2) # 从 1 开始，到 21 之前结束，步长为 2
print(arr3)

# 需要创建同值数组时，使用 np.zeros( ) 函数以及 np.ones( ) 函数，如示例
# 全 0 数组
arr1 = np.zeros( 3 ) # 形状为 3 的向量
print(arr1)
# 全 1 数组
arr2 = np.ones( (1,3) ) # 形状为(1,3)的矩阵
print(arr2)
# 全 3.14 数组
arr3 = 3.14 * np.ones( (2,3) ) # 形状为(2,3)的矩阵
print(arr3)
# 示例中隐藏了一个细节——两个函数输出的并不是整数型的数组，这可能是为了避免插进去的浮点数被截断，所以将其设定为浮点型数组。

# 有时需要创建随机数组，那么可以使用 np.random 系列函数，如示例所示。
# 0-1 均匀分布的浮点型随机数组
arr1 = np.random.random( 5 ) # 形状为 5 的向量
print( arr1 )
# 整数型随机数组
arr2 = np.random.randint( 10,100,(1,15) ) # 形状为(1,15)的矩阵
print( arr2 )
# 服从正态分布的随机数组
arr3 = np.random.normal( 0,1,(2,3) ) # 形状为(2,3)的二维矩阵
print( arr3 )

# 与 Python 列表一致，访问 NumPy 数组元素时使用中括号，索引由 0 开始。
# 访问单个元素时，向量用 arr1[x]，矩阵用 arr2[x,y]。逗号在矩阵里用于区分行与列
# 访问元素
print( arr1[3] ) # 正着访问
print( arr1[-1] ) # 倒着访问
# 创建矩阵
arr2 = np.array( [ [1,2,3],[4,5,6] ] )
print(arr2)
# 访问元素
print( arr2[0,2] )
print( arr2[1,-2] )

# 矩阵的花式索引（Fancy indexing），普通索引用一层中括号，花式索引用两层中括号。
# 创建向量
arr1 = np.arange(0,90,10)
print(arr1)
# [ 0 10 20 30 40 50 60 70 80]
# 花式索引
print( arr1[ [0,2] ] )
# [0 20]

# 创建矩阵
arr2 = np.arange(1,17).reshape(4,4)
print(arr2)
# [[01 02 03 04]
# [05 06 07 08]
# [09 10 11 12]
# [13 14 15 16]]
# 花式索引
print( arr2[ [0,1] , [0,1] ] )
print( arr2[ [0,1,2] , [2,1,0] ] )
# [1 6]
# [3 6 9]
# 修改数组元素
arr2[ [0,1,2,3] , [3,2,1,0] ] = 100
print(arr2)
# [[001 002 003 100]
# [005006 100 008]
# [009 100 011 012]
# [100 014 015 016]]

# 普通索引 arr1[ x1 ]       arr2[ x1 , y1 ]
# 花式索引 arr1[ [x1,x2,…,xn] ]        arr2[ [x1,x2,…,xn] , [y1,y2,…,yn] ]

# 数组的切片
# 向量的切片
arr1 = np.arange(10)
print( arr1 )
# 当明确知道从第 x 个元素切到第 y 个元素，如示例所示。
print( arr1[ 1 : 4 ] ) # 从索引[1]开始，切到索引[4]之前
print( arr1[ 1 : ] ) # 从索引[1]开始，切到结尾
print( arr1[ : 4 ] ) # 从数组开头开始，切到索引[4]之前
# [0 1 2 3 4 5 6 7 8 9]
# [1 2 3]
# [1 2 3 4 5 6 7 8 9]
# [0 1 2 3]

# 当明确切除数组的开头与结尾，如示例所示。
# print( arr1[ 2 : -2 ] ) # 切除开头 2 个和结尾 2 个
# print( arr1[ 2 : ] ) # 切除开头 2 个
# print( arr1[ : -2 ] ) # 切除结尾 2 个

# 当明确隔几个元素采样一次时，示例如下。
# print( arr1 )
# print( arr1[ : : 2 ] ) # 每 2 个元素采样一次
# print( arr1[ : : 3 ] ) # 每 3 个元素采样一次
# print( arr1[ 1 : -1 : 2 ] ) # 切除一头一尾后，每 2 个元素采样一次

# 矩阵的切片

arr2 = np.arange(1,21).reshape(4,5)
print( arr2 )
# [[ 01 02 03 04 05]
# [06 07 08 09 10]
# [11 12 13 14 15]
# [16 17 18 19 20]]
print( arr2[ 1:3 , 1:-1 ] ) # 矩阵切片初体验
# [[ 07 08 09]
# [12 13 14]]
print( arr2[ ::3 , ::2 ] ) # 跳跃采样
# [[ 01 03 05]
# [16 18 20]]

# 基于矩阵的切片功能，我们可以提取其部分行，如示例所示。
print( arr2[ 2 , : ] ) # 提取第 2 行
# [11 12 13 14 15]
print( arr2[ 1:3 , : ] ) # 提取 1 至 2 行
# [[06 07 08 09 10]
# [11 12 13 14 15]]
# 基于矩阵的切片功能，我们可以提取其部分列，如示例所示
print( arr2[ : , 2 ] ) # 提取第 2 列（注意，输出的是向量）
# [3 8 13 18]
cut = arr2[:,2]  # 提取第 2 列为向量
cut = cut.reshape( (-1,1) ) # 升级为列矩阵
print(cut)
print( arr2[ : , 1:3 ] ) # 提取 1 至 2 列
# [[ 02 03]
# [07 08]
# [12 13]
# [17 18]]
# 值得注意的是，提取某一个单独的列时，出来的结果是一个向量。其实这么做只是为了省空间，我们知道，列矩阵必须用两层中括号来存储，而形状为 1000
# 的向量，自然比形状为(1000,1)的列矩阵更省空间（节约了 1000 对括号）。


# 与 Python 列表和 Matlab 不同，NumPy 数组的切片仅仅是原数组的一个视图。换言之，NumPy 切片并不会创建新的变量
# 如果真的需要为切片创建新变量（这种情况很稀少），使用 .copy( ) 方法。
arr = np.arange(10) # 创建一个 0 到 10 的向量 arr
print(arr)
copy = arr[ : 3 ] .copy() # 创建 arr 的拷贝切片
# [0 1 2]
print(copy)

# 与 NumPy 数组的切片一样，NumPy 数组完整的赋值给另一个数组，也只是绑定。换言之，NumPy 数组之间的赋值并不会创建新的变量，示例如下。
arr1 = np.arange(10) # 创建一个 0 到 10 的数组变量 arr
print(arr1)
arr2 = arr1 # 把数组 1 赋值给另一个数组 2
print(arr2)
arr2[0] = 100 # 修改数组 2
print(arr2)
print(arr1) # 原数组也被修改
# 如果真的需要赋给一个新数组，使用 .copy( ) 方法。

# 矩阵的变形
arr1 = np.array( [ [1,2,3],[4,5,6] ] ) # 创建矩阵
print(arr1)
arr2 = arr1.reshape(6) # 变形为向量
print(arr2)
arr3 = arr1.reshape(1,6) # 变形为矩阵
print(arr3)

# 数组的拼接
# 两个向量拼接，将得到一个新的加长版向量。
# 创建向量 1
arr1 = np.array( [1,2,3] )
# 创建向量 2
arr2 = np.array( [4,5,6] )
# 拼接
arr3 = np.concatenate( [arr1,arr2] )
print(arr3)
# 矩阵的拼接
# 创建数组 1
arr1 = np.array( [[1,2,3],[ 4,5,6]] )
print(arr1)
# 创建数组 2
arr2 = np.array( [[7,8,9],[10,11,12]] )
print(arr2)
# 按第一个维度（行）拼接
arr3 = np.concatenate( [arr1,arr2] ) # 默认参数 axis=0
print(arr3)
# [[01 02 03]
# [04 05 06]
# [07 08 09]
# [10 11 12]]
 # 按第二个维度（列）拼接
arr4 = np.concatenate( [arr1,arr2] ,axis=1 )
print(arr4)
# [[01 02 03 07 08 09]
# [04 05 06 10 11 12]]
# 向量和矩阵不能进行拼接，必须先把向量升级为矩阵

# 数组的分裂
# 向量分裂，将得到若干个更短的向量
# 创建向量
arr = np.arange(10,100,10)
print(arr)
# 分裂数组
arr1,arr2,arr3 = np.split( arr , [2,8] )
print(arr1)
print(arr2)
print(arr3)
# np.split()函数中，给出的第二个参数[2,8]表示在索引[2]和索引[8]的位置截断。

# 矩阵的分裂同样可以按不同的维度进行，分裂出来的均为矩阵
# 创建矩阵
arr = np.arange(1,9).reshape(2,4)
print(arr)
# [[1 2 3 4]
# [5 6 7 8]]
# 按第一个维度（行）分裂
arr1,arr2 = np.split(arr,[1]) # 默认参数 axis=0
print(arr1 , '\n\n' , arr2) # 注意输出的是矩阵
# [[1 2 3 4]]
# [[5 6 7 8]]
# 按第二个维度（列）分裂
arr1,arr2,arr3 = np.split( arr , [1,3] , axis=1 )
print( arr1 , '\n\n' , arr2 , '\n\n' , arr3 )
# [[1]
# [5]]
# [[2 3]
# [6 7]]
# [[4]
# [8]]

# NumPy 和 PyTorch 的基础语法几乎一致，具体表现为：
# np 对应 torch；
# 数组 array 对应张量 tensor；
# NumPy 的 n 维数组对应着 PyTorch 的 n 阶张量。
# 数组与张量之间可以相互转换：
# 数组 arr 转为张量 ts：ts = torch.tensor(arr)；
# 张量 ts 转为数组 arr：arr = np.array(ts)。